ETF vs. versicherungsbasierte Altersvorsorge¶
import pandas as pd
import math
from datetime import datetime
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.io as pio
pio.renderers.default='notebook'
Motivation¶
🇬🇧🇺🇸 In Germany there are two major models to supplement your retirement fund. Either self managed (which is more heavily taxed during payout) or mediated through a life-insurance agency (which has higher upfront and running cost). When researching this topic in detail for my family, I found that there is almost no quantitative cost breakdown / framework that allows you to run the numbers yourself. This notebook is an attempt. Use at your own risk! Since it's a German thing with lots of German terminology, let's just speak and "ausnahmsweise" also program in German here this time.
🇩🇪 Ziel dieser Übung soll es sein, eigen-gemanagte Altersvorsorge über einen ETF vs. über eine Altersvorsorgeversicherung möglichst genau quantitativ zu vergleichen. Im Internet finden sich hier nur qualitative Aussagen. Dieses Python-Notebook ist der Versuch, Simulationen beider Strategien zu vergleichen. Nutzung ohne Gewähr!
Insbesondere die steuerliche Kalkulation mit Abgeltungspauschalen, daraus resultierenden Steuerguthaben und Steuerfreibeträgen, macht die Berechnung recht umständlich, weshalb eine Excel-Tabelle hier ungeeignet und stattdessen ein Python Notebook entworfen wurde, welches die Anspar- und Rentenphase simuliert und die Statistiken über Data-Frames wegschreibt und dann per Plotly ausgibt.
Annahmen¶
- Wir gehen von einer konstanten Sparrate pro Monat, einer (einzigen) durchschnittlichen Rendite der ETFs pro Jahr aus.
- Wir besparen mehrer unterschiedliche ETFs und folgen die s.g. "3x10-Regel": hierbei wird ca. alle 10 Jahre ein anderer ETF bespart und die ETFs in der Auszahlungsphase genau umgekehrt wieder verkauft. So ist der Gewinnanteil bei den zuerst verkauften ETFs weniger hoch, und die daraus resultierenden Steuern fallen erst später an und fehlen nicht bei der Renditegenerierung.
- Die Steuerpolitik (Abgeltungssteuer, Teilfreistellung der ETFs, Kapitalfreibetrag) wird als über den gesamten Zeitraum konstant angenommen.
- Mit derselben Sparrate wird ein Versicherungsvertrag gerechnet, der mit Abschluss & Vertriebskosten über die ersten paar Jahre gerechnet wird.
- Zusätzlich schlagen über die gesamte Laufzeit beitragsbezogene Verwaltungskosten, guthabenbezogene Verwaltungskosten und Stückkosten pro Jahr zu buche.
- Das aus den "Versicherung minus Kosten" entstehende Versicherungsdepot wird zu Beginn der Rentenzeit in eine lebenslange Rente aufgeteilt und gemäß der aktuellen Sterbetafeln auf Monate verteilt. Zusätzlich wird eine konservative prozentuale Rentensteigerung pro Jahr angenommen.
- Um die Anlagestrategien effizient vergleichen zu können, wird genau dieser über die Versicherung zustehende Betrag zzgl. der entstehenden Steuern in der Rentenphase monatlich aus dem privaten Depot entnommen.
- In der Rentenphase werden die ETFs nicht mehr umgeschichtet - hier lassen sich zusätzlich konservative Krisensituationen rechnen (Kurseinbrüche auf einen bestimmten Prozentsatz des vorherigen Kurswertes).
Die Frage ist dann: schlägt die Versicherung den ETF oder umgekehrt? Wann ginge der ETF-basierten Altersvorsorge das Geld aus? Vor oder nach der durch die Sterbetafel veranschlagten Lebenserwartung?
# ETFs
start_ab_monat: int = 6 # Monat, ab dem im Ersten Jahr der Ansparphase eingezahlt wird
einzahlung_pro_monat: float = 258.48 # EUR # monatlicher Einzahlungsbetrag in der Ansparphase
durchschnittl_rendite: float = 0.06 # erwartete Wertsteigerung der ETFs pro Jahr
durchschnittl_basiszins: float = 0.0255 # wird von Notenbank festgelegt und soll Renditesteigerung durch Thessaurierung von Dividenden simulieren
teilfreistellung_etfs: float = 0.3 # ETFs sind zu 30% von der Abgeltungssteuer freigestellt, ersetzt die vormalige Praxis, sich die Quellensteuer anrechnen zu lassen
abgeltungssteuer: float = 0.2636 # Kapitelertragssteuer
ansparphase: tuple[int, int] = (2024, 2049) # Start- und Endjahr der Ansparphase
kapitalfreibetrag: float = 1000.00 # EUR # Kapitalerträge, die p.a. von der Abgeltungssteuer befreit sind
etfs_im_zeitraum: list[tuple[int, int]] = [(2024, 2031), (2032, 2038), (2039, 2049)] # "3x10 Regel", Zeiträume, in dnen jeder der einzelnen ETFs bespart wird
etf_farben_dunkel = px.colors.qualitative.Dark2 # dunkle Farbskala zur visualisierung der ETFs im Diagramm
etf_farben_hell = px.colors.qualitative.Set2 # helle Farbskala zur visualisierung der ETFs im Diagramm
# Versicherung
anzahl_monate: int = (ansparphase[1]-ansparphase[0]+1)*12 + (12-start_ab_monat) # Anzahl der Monate, die gespart wird
gesamtbeitrag: float = einzahlung_pro_monat * anzahl_monate # Gesamtbeitrag, der in eine Versicherung über die Laufzeit eingezahlt wird
abschl_vertriebsk_proz: float = 0.025 # Abschluss- & Vertriebskosten (prozentual vom Beitrag)
abschl_vertriebsk_monat: float = gesamtbeitrag * abschl_vertriebsk_proz /5/12 # Abschluss- und Vertriebskosten pro Monat in den ersten 5 Jahren
beitragsbez_verwalt_k: float = 0.052 # Verwaltungskosten prozentual vom Beitrag
guthabensbez_verwalt_k: float = 0.0025 # Verwaltungskosten prozentual vom Guthaben
stueckkosten_verwalt_k: float = 30.0 # EUR # Verwaltungskosten, Stückkosten pro Jahr
lebenserwartung: float = 85.18 # Lebenserwartung laut Sterbetafel
in_rente_mit: float = 65+(5.0/12)-(5.0/365) # Alter beim Eintritt in die Rente
rentensteigerung_pa: float = 0.03 # prozentuale Rentenerhöhung pro Jahr aufgr. konservativer Geldanlage des Versicherungsdepots
max_jahr: int = 2085 # maximale Betractung
# Alternativrechnung, Börsencrashes mit Jahren und Zielwert, auf den alle Börsenwerte fallen
krisensimulationen: list[list[dict[int, float]]] = [
{2065: 0.6, 2075: 0.6},
{2055: 0.6, 2075: 0.6}
]
# Alternativrechnung, höhere Abgeltungssteuer analog der Einkommenssteuer
sim_alt_abgeltungssteuer: float = 0.42
Datentypen¶
Die folgenden Datenstrukturen modellieren ein Depot mit mehreren Wertpapieren und bieten Funktionen, um Statistiktabellen zum Zweck der visualisierung zu erstellen.
class Anteil:
kaufpreis: float
kaufdatum: datetime
akt_preis: float
def __init__(self, kaufkurs: float, kaufdatum: datetime):
self.kaufpreis = kaufkurs
self.akt_preis = kaufkurs
self.kaufdatum = kaufdatum
def __repr__(self) -> str:
return f"{self.kaufpreis:.2f}/{self.akt_preis:.2f}/{self.kaufdatum:%Y-%m}"
class Depot:
etfs: list[list[Anteil]]
def __init__(self):
self.etfs = [[] for i in range(0, len(etfs_im_zeitraum))]
# Statistiktabelle vorbereiten: Ansparphase
def make_anspar_stats() -> pd.DataFrame:
anspar_stats = pd.DataFrame()
anspar_stats["datum"] = pd.Series([
datetime(jahr, monat, 1)
for jahr in range(ansparphase[0], ansparphase[1]+1)
for monat in range(1, 13)
if jahr != ansparphase[0] or monat >= start_ab_monat
])
anspar_stats["steuerguthaben"] = pd.Series(dtype="float")
anspar_stats["versicherungsguthaben"] = pd.Series(dtype="float")
anspar_stats["versicherungsbeitraege"] = pd.Series(dtype="float")
for etf_nr in range(0, len(etfs_im_zeitraum)):
anspar_stats[f"etf_{etf_nr}_beitraege"] = pd.Series(dtype="float")
anspar_stats[f"etf_{etf_nr}_wachstum"] = pd.Series(dtype="float")
anspar_stats = anspar_stats.set_index(anspar_stats["datum"])
anspar_stats.drop(columns=["datum"], inplace=True)
return anspar_stats
# Statistiktabelle vorbereiten: Rentenphase
def make_renten_stats() -> pd.DataFrame:
renten_stats = pd.DataFrame()
renten_stats["datum"] = pd.Series([
datetime(jahr, monat, 1)
for jahr in range(ansparphase[1]+1, max_jahr+1)
for monat in range(1, 13)
])
renten_stats["steuerguthaben"] = pd.Series(dtype="float")
renten_stats["gezahlte_steuern"] = pd.Series(dtype="float")
renten_stats["monatliche_rente"] = pd.Series(dtype="float")
for etf_nr in range(0, len(etfs_im_zeitraum)):
renten_stats[f"etf_{etf_nr}_beitraege"] = pd.Series(dtype="float")
renten_stats[f"etf_{etf_nr}_wachstum"] = pd.Series(dtype="float")
renten_stats = renten_stats.set_index(renten_stats["datum"])
renten_stats.drop(columns=["datum"], inplace=True)
return renten_stats
Ansparphase¶
Der folgende Code simuliert die Ansparphase. Jeden Monat in der Ansparphase geschieht folgendes: Wir rechnen sowohl ein privates Depot mit mehreren ETFs ("3x10 Regel"), führt dazu bereits die Abgeltungspauschale ab, welche sich als Steuerguthaben gemerkt wird, und bedient parallel dazu einen Versicherungsvertrag, welcher am Ende durch ein potentielles Versicherungsguthaben repräsentiert wird.
def run_ansparphase(depot: Depot, alt_abgeltungssteuer: float = None) -> tuple[float, float, pd.DataFrame]:
if alt_abgeltungssteuer is None:
alt_abgeltungssteuer = abgeltungssteuer
anspar_stats = make_anspar_stats()
steuerguthaben: float = 0.0
versicherungsguthaben: float = 0.0
versicherungsbeitraege: float = 0.0
for jahr in range(ansparphase[0], ansparphase[1]+1):
for monat in range(1, 13):
datum = datetime(jahr, monat, 1)
# Starte erstes Jahr erst ab konfiguriertem Monat
if jahr == ansparphase[0] and monat < start_ab_monat:
continue
# Im Januar: Steuern auf alle ETFs zahlen
if monat == 1:
kapitalertraege = 0
for etf_nr, etf_anteile in enumerate(depot.etfs):
etf_anteilswert = sum(etf_anteil.akt_preis for etf_anteil in etf_anteile)
kapitalertraege += etf_anteilswert * durchschnittl_basiszins * (1-teilfreistellung_etfs)
if kapitalertraege > kapitalfreibetrag:
faellige_steuer = (kapitalertraege-kapitalfreibetrag) * alt_abgeltungssteuer
steuerguthaben += faellige_steuer
# Neue Anteile kaufen
for etf_nr, etf_zeitraum in enumerate(etfs_im_zeitraum):
if jahr >= etf_zeitraum[0] and jahr <= etf_zeitraum[1]:
depot.etfs[etf_nr].append(Anteil(einzahlung_pro_monat, datum))
# Versicherung besparen
versicherungsbeitraege += einzahlung_pro_monat
versicherungsguthaben += (1-beitragsbez_verwalt_k) * einzahlung_pro_monat
if datum < datetime(ansparphase[0]+5, monat, 1):
versicherungsguthaben -= abschl_vertriebsk_monat
versicherungsguthaben *= (1+durchschnittl_rendite)**(1/12)
# Versicherung: Jährliche Kosten abziehen
if monat == start_ab_monat and jahr > ansparphase[0]:
versicherungsguthaben -= guthabensbez_verwalt_k * versicherungsguthaben
versicherungsguthaben -= stueckkosten_verwalt_k
# Fonds wachsen lassen und Statistiken aktualisieren
anspar_stats.loc[datum, "steuerguthaben"] = steuerguthaben
anspar_stats.loc[datum, "versicherungsguthaben"] = versicherungsguthaben
anspar_stats.loc[datum, "versicherungsbeitraege"] = versicherungsbeitraege
for etf_nr, etf_anteile in enumerate(depot.etfs):
etf_beitraege: float = 0.0
etf_wachstum: float = 0.0
for etf_anteil in etf_anteile:
etf_anteil.akt_preis *= (1+durchschnittl_rendite)**(1/12)
etf_beitraege += etf_anteil.kaufpreis
etf_wachstum += etf_anteil.akt_preis - etf_anteil.kaufpreis
anspar_stats.loc[datum, f"etf_{etf_nr}_beitraege"] = etf_beitraege
anspar_stats.loc[datum, f"etf_{etf_nr}_wachstum"] = etf_wachstum
anspar_stats["depot_gesamtwert"] = (
sum(anspar_stats[f"etf_{etf_nr}_beitraege"] + anspar_stats[f"etf_{etf_nr}_wachstum"] for etf_nr in range(0, len(depot.etfs)))
)
return versicherungsguthaben, steuerguthaben, anspar_stats
depot: Depot = Depot()
versicherungsguthaben, steuerguthaben, anspar_stats = run_ansparphase(depot)
Ansparphase - Ergebnis¶
Die folgende Grafik beinhaltet die 3 ETFs ("3x10 Regel") und zeigt Eigenanteil (dunkel) und Wertsteigerung (hell). Durch grüne und rote Striche Versicherungsbeiträge dargestellt.
def viz_ansparphase(anspar_stats: pd.DataFrame, zusaetzlicher_titel: str = ""):
fig = go.Figure()
x = anspar_stats.index
fig.add_trace(go.Bar(
x=x, y=-anspar_stats["steuerguthaben"], name="Steuerguthaben",
hovertemplate = '%{y:.2f}€', marker={"color": "gray"})
)
for etf_nr, etf_zeitraum in enumerate(etfs_im_zeitraum):
fig.add_trace(go.Bar(
x=x, y=anspar_stats[f"etf_{etf_nr}_beitraege"], name=f"ETF {etf_nr} Beiträge",
hovertemplate="%{y:.2f}€", marker={"color": etf_farben_dunkel[etf_nr]}
))
fig.add_trace(go.Bar(
x=x, y=anspar_stats[f"etf_{etf_nr}_wachstum"], name=f"ETF {etf_nr} Wachstum",
hovertemplate="%{y:.2f}€", marker={"color": etf_farben_hell[etf_nr]}
))
fig.add_vline(x=datetime(etf_zeitraum[0], 1 if etf_nr != 0 else start_ab_monat, 1), line_color=etf_farben_dunkel[etf_nr])
fig.add_trace(go.Scatter(
x=x, y=anspar_stats["depot_gesamtwert"], name="Depot-Gesamtwert",
mode="markers", fillcolor="DarkSlateGrey", line={"color": "DarkSlateGrey"},
hovertemplate = '%{y:.2f}€', marker={"symbol": "line-ew", "line": {"width": 2, "color": "DarkSlateGrey"}}
))
fig.add_trace(go.Scatter(
x=x, y=anspar_stats["versicherungsguthaben"], name="Versicherungsguthaben",
mode="markers", fillcolor="red", line={"color": "red"},
hovertemplate = '%{y:.2f}€', marker={"symbol": "line-ew", "line": {"width": 2, "color": "red"}}
))
fig.add_trace(go.Scatter(
x=x, y=anspar_stats["versicherungsbeitraege"], name="(Versicherungs-)Beiträge",
mode="markers", fillcolor="green", line={"color": "green"},
hovertemplate = '%{y:.2f}€', marker={"symbol": "line-ew", "line": {"width": 2, "color": "green"}}
))
fig.update_layout(barmode='relative', title_text=f"Ansparverlauf und Steuerguthaben {zusaetzlicher_titel}")
fig.update_xaxes(tickangle=90)
fig.update_layout(xaxis = dict(
tickmode="array",
tickvals=[datetime(jahr, 1, 1) for jahr in range(ansparphase[0], ansparphase[1]+1) if jahr != ansparphase[0] or start_ab_monat == 1],
ticktext=[jahr for jahr in range(ansparphase[0], ansparphase[1]+1) if jahr != ansparphase[0] or start_ab_monat == 1]
))
fig.update_layout(hovermode='x unified')
fig.show()
viz_ansparphase(anspar_stats)
Rentenphase¶
Der folgende Code simuliert die Rentenphase. Jeden Monat wird folgendes gemacht:
- Eine Basisrente wird ausgezahlt welche der Rente entspricht, die man bei einem entsprechenden Altersvorsorge-Versicherungsvertrag bekommen würde.
- Im Januar wird weiterhin die Abgeltungspauschale aufs aktuelle Depot abgeführt. Dadurch entsteht ein zusätzlicher Entnahmebedarf.
- Durch die Entnahmen selbst entsteht eine Steuerlast. Der Entnahmeprozess wird rekursiv durchgeführt, bis die Entnahme/Steuerlast vernachlässigbar (<2€) ist.
- Die Entnahme geschieht nach der "3x10 Regel" zuerst für den zuletzt erworbenen ETF um die Steuerlast vor allem am Anfang der Rentenphase zu senken.
- Wie in der Ansparphase wachsen die Wertpapiere weiter.
- Bei Bedarf kann zur Mitte festgelegter Jahre eine Krise mit einem Kurseinbruch auf einen bestimmten Prozentsatz simuliert werden.
def run_rentenphase(
depot: Depot, versicherungsguthaben: float, steuerguthaben: float,
krisenabstuerze: dict[int, float] = None, alt_abgeltungssteuer: float = None, verbose: bool = False
) -> pd.DataFrame:
if alt_abgeltungssteuer is None:
alt_abgeltungssteuer = abgeltungssteuer
renten_stats = make_renten_stats()
monatliche_rente = versicherungsguthaben/(lebenserwartung-in_rente_mit)/12
for jahr in range(ansparphase[1]+1, max_jahr+1):
freibetrag_dieses_jahr = kapitalfreibetrag
for monat in range(1, 13):
datum = datetime(jahr, monat, 1)
entnahme: float = 0.0
bedarf: float = monatliche_rente
# Im Januar: Abgeltungspauschale
gezahlte_steuern: float = 0.0
if monat == 1:
kapitalertraege = 0
for etf_nr, etf_anteile in enumerate(depot.etfs):
etf_anteilswert = sum(etf_anteil.akt_preis for etf_anteil in etf_anteile)
kapitalertraege += etf_anteilswert * durchschnittl_basiszins * (1-teilfreistellung_etfs)
if kapitalertraege > freibetrag_dieses_jahr:
faellige_steuern = (kapitalertraege-freibetrag_dieses_jahr) * alt_abgeltungssteuer
freibetrag_dieses_jahr = 0
gezahlte_steuern += faellige_steuern
if gezahlte_steuern > 2.00:
bedarf += faellige_steuern
else:
freibetrag_dieses_jahr -= kapitalertraege
# Mitte des Jahres Krise simulieren
if monat == 6 and krisenabstuerze is not None and (kurssturz := krisenabstuerze.get(jahr, None)) is not None:
if verbose:
print(f"Krise!! Alle Kurse werden auf {kurssturz} herunter korrigiert!")
for etf_anteile in depot.etfs:
for etf_anteil in etf_anteile:
etf_anteil.akt_preis *= kurssturz
# Wertpapiere verkaufen
while bedarf-entnahme > 2.00:
# Zuletzt gekaufte ETF_Nr zuerst verkaufen (hat noch den wenigsten Gewinn angesammelt
# und verursacht damit die geringsten Steuern)
for etf_nr, etf_anteile in reversed(list(enumerate(depot.etfs))):
if len(etf_anteile) > 0:
break
# Geld alle, nichts mehr vom Depot zu entnehmen
if len(etf_anteile) == 0:
break
# Entnehme aus aktuellem ETF_Nr
fifo_etf_anteil = etf_anteile[0]
verbleibender_bedarf = bedarf-entnahme
moegliche_entnahme = min(fifo_etf_anteil.akt_preis, verbleibender_bedarf)
fifo_etf_gewinnanteil = (fifo_etf_anteil.akt_preis-fifo_etf_anteil.kaufpreis) / fifo_etf_anteil.akt_preis
realisierter_gewinn = fifo_etf_gewinnanteil * moegliche_entnahme
if verbose:
print(
f"{jahr}-{monat:02d}: Restbedarf {verbleibender_bedarf:.2f}: Verkaufe von {fifo_etf_anteil} aus ETF {etf_nr}: {moegliche_entnahme:.2f} mit {realisierter_gewinn:.2f} Gewinnanteil. "
+ ("ETF-Anteil damit aufgebraucht." if fifo_etf_anteil.akt_preis == moegliche_entnahme else "Restanteil verbleibt.")
)
fifo_etf_anteil.akt_preis -= moegliche_entnahme
fifo_etf_anteil.kaufpreis -= (1-fifo_etf_gewinnanteil) * moegliche_entnahme
entnahme += moegliche_entnahme
if fifo_etf_anteil.akt_preis == 0:
depot.etfs[etf_nr].pop(0)
# Steuern auf Verkauf zahlen, zuerst Steuerguthaben und Freibetrag einsetzen, dann Bedarf
# erhöhen, um Steuerlast aus dem ETF zu decken. Dadurch evtl. Bedarf erhöhen, bis nur noch 2€ Steuern
# gezahlt werden müssen.
steuerpflichtiger_gewinn = (1-teilfreistellung_etfs) * realisierter_gewinn
if freibetrag_dieses_jahr > steuerpflichtiger_gewinn:
freibetrag_dieses_jahr -= steuerpflichtiger_gewinn
else:
steuerpflichtiger_gewinn -= freibetrag_dieses_jahr
freibetrag_dieses_jahr = 0
faellige_steuern = alt_abgeltungssteuer * steuerpflichtiger_gewinn
if steuerguthaben > faellige_steuern:
steuerguthaben -= faellige_steuern
else:
faellige_steuern -= steuerguthaben
steuerguthaben = 0
gezahlte_steuern += faellige_steuern
if gezahlte_steuern > 2.00:
bedarf += faellige_steuern
# Wertpapiere wachsen lassen
renten_stats.loc[datum, "steuerguthaben"] = steuerguthaben
renten_stats.loc[datum, "gezahlte_steuern"] = gezahlte_steuern
renten_stats.loc[datum, "monatliche_rente"] = monatliche_rente
for etf_nr, etf_anteile in enumerate(depot.etfs):
etf_beitraege: float = 0.0
etf_wachstum: float = 0.0
for etf_anteil in etf_anteile:
etf_anteil.akt_preis *= (1+durchschnittl_rendite)**(1/12)
etf_beitraege += etf_anteil.kaufpreis
etf_wachstum += etf_anteil.akt_preis - etf_anteil.kaufpreis
renten_stats.loc[datum, f"etf_{etf_nr}_beitraege"] = etf_beitraege
renten_stats.loc[datum, f"etf_{etf_nr}_wachstum"] = etf_wachstum
monatliche_rente *= (1+rentensteigerung_pa)
renten_stats["depot_gesamtwert"] = (
sum(renten_stats[f"etf_{etf_nr}_beitraege"] + renten_stats[f"etf_{etf_nr}_wachstum"] for etf_nr in range(0, len(depot.etfs)))
)
return renten_stats
depot = Depot()
versicherungsguthaben, steuerguthaben, anspar_stats = run_ansparphase(depot)
renten_stats_haupt = run_rentenphase(depot, versicherungsguthaben, steuerguthaben)
Rentenphase - Ergebnis¶
Das folgende Diagramm zeigt das langsame "Abfließen" des Depotwertes und als vertikale Linie die vergleichsweise Lebenserwartung lt. Sterbetafel.
def viz_rentenphase(renten_stats: pd.DataFrame, zusaetzlicher_titel: str = "", zeige_variante: bool = False):
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing = 0.05, row_heights=[0.70, 0.30])
x = renten_stats.index
fig.add_trace(go.Bar(
x=x, y=-renten_stats["steuerguthaben"], name="Steuerguthaben",
hovertemplate = '%{y:.2f}€', marker={"color": "gray"}
), row=1, col=1)
for etf_nr, _ in enumerate(etfs_im_zeitraum):
fig.add_trace(go.Bar(
x=x, y=renten_stats[f"etf_{etf_nr}_beitraege"], name=f"ETF {etf_nr} Beiträge",
hovertemplate="%{y:.2f}€", marker={"color": etf_farben_dunkel[etf_nr]}
), row=1, col=1)
fig.add_trace(go.Bar(
x=x, y=renten_stats[f"etf_{etf_nr}_wachstum"], name=f"ETF {etf_nr} Wachstum",
hovertemplate="%{y:.2f}€", marker={"color": etf_farben_hell[etf_nr]}
), row=1, col=1)
fig.add_trace(go.Scatter(
x=x, y=renten_stats["depot_gesamtwert"], name="Depot-Gesamtwert",
mode="markers", fillcolor="DarkSlateGrey", line={"color": "DarkSlateGrey"},
hovertemplate = '%{y:.2f}€', marker={"symbol": "line-ew", "line": {"width": 2, "color": "DarkSlateGrey"}}
), row=1, col=1)
fig.add_trace(go.Scatter(
x=x, y=renten_stats["gezahlte_steuern"], name="Gezahlte Steuern", mode="markers",
hovertemplate = '%{y:.2f}€', marker={"color": "pink"}
), row=2, col=1)
fig.add_trace(go.Scatter(
x=x, y=renten_stats["monatliche_rente"], name="Monatliche Rente", mode="markers",
hovertemplate = '%{y:.2f}€', marker={"color": "turquoise"}
), row=2, col=1)
if zeige_variante:
fig.add_trace(go.Scatter(
x=x, y=renten_stats["etf_var_beitraege"], name="Beiträge ETF-aus-Versicherung",
mode="markers", fillcolor="red", line={"color": "red"},
hovertemplate = '%{y:.2f}€', marker={"symbol": "line-ew", "line": {"width": 2, "color": "red"}}
))
fig.add_trace(go.Scatter(
x=x, y=renten_stats["etf_var_beitraege"]+renten_stats["etf_var_wachstum"], name="Depotwert ETF-aus-Versicherung",
mode="markers", fillcolor="purple", line={"color": "purple"},
hovertemplate = '%{y:.2f}€', marker={"symbol": "line-ew", "line": {"width": 2, "color": "purple"}}
))
fig.add_vline(x=datetime(ansparphase[1]+1+math.ceil(lebenserwartung-in_rente_mit), 1, 1), line_color='gray', name="Lebenserwartung")
fig.update_layout(barmode='relative', title_text=f"ETF-Depotverlauf zur Rente {zusaetzlicher_titel}")
for row in [1, 2]:
fig.update_xaxes(
tickmode="array", tickangle=90, row=row,
tickvals=[datetime(jahr, 1, 1) for jahr in range(ansparphase[1]+1, max_jahr+1)],
ticktext=[jahr for jahr in range(ansparphase[1]+1, max_jahr+1)]
)
fig.update_layout(hovermode='x unified')
fig.show()
viz_rentenphase(renten_stats_haupt)
Alternativrechnung mit simulierten Börsenkrisen in der Rente¶
Die folgenden Beispiele ziehen Kurseinbrüche während der Rentenphase mit in Betracht. Wenn stärkere Einbrüche in den ersten 10 Jahren der Rente stattfinden, kann eine konservativere Anlage über eine Rentenversicherung besser abschneiden.
for krisenabstuerze in krisensimulationen:
depot = Depot()
versicherungsguthaben, steuerguthaben, anspar_stats = run_ansparphase(depot)
renten_stats = run_rentenphase(depot, versicherungsguthaben, steuerguthaben, krisenabstuerze)
viz_rentenphase(renten_stats, f"Krisenabstürze zu/auf {krisenabstuerze}")
Alternativrechnung mit höherer Abgeltungssteuer¶
Wir simulieren ein weiteres Beispiel, in dem die Kapitalertragssteuer auf das Niveau der Einkommenssteuer angehoben wird. Trotzdem reicht das Ersparte aus den ETF-Fonds i.d.R. länger als die Lebenserwartung lt. Sterbetafel.
depot = Depot()
versicherungsguthaben, steuerguthaben, anspar_stats = run_ansparphase(depot, alt_abgeltungssteuer=sim_alt_abgeltungssteuer)
viz_ansparphase(anspar_stats, f"alternative Kapitalertragssteuer {sim_alt_abgeltungssteuer*100:.0f}%")
renten_stats_alt = run_rentenphase(depot, versicherungsguthaben, steuerguthaben, alt_abgeltungssteuer=sim_alt_abgeltungssteuer)
viz_rentenphase(renten_stats_alt, f"alternative Kapitalertragssteuer {sim_alt_abgeltungssteuer*100:.0f}%")
Variante: Umwandlung des Versicherungsguthabens in ETF-Depot¶
Eine Lebensversicherung ermöglicht statt des Auszahlens einer regelmäßigen Rente auch die sofortige Auszahlung am Rentenbeginn. Dieser Betrag steht zu Renteneintritt vollständig steuerfrei zur Verfügung, da er jedoch aufgrund der Versicherungskosten niedriger ausfällt als das Guthaben eines selbst gemanagten Depots, steht hier ein niedrigerer Betrag zur Wertsteigerung durch Zinseszins zur Verfügung. Im Laufe der Jahre wird auch hier wieder Rente entnommen, es fallen aber erst in den späteren Jahren Steuern an, und zwar nur auf die Gewinne, die während der Rentenzeit selbst erzielt wurden. Trotzdem kann die Variante nicht besser performen, als ein komplett selbst gemanagtes Depot: so reicht die Depotsumme im Vorliegenden Beispiel 1-3 Jahre kürzer, je nachdem welche Kapitalertragssteuer man annimmt.
depot = Depot()
versicherungsguthaben, steuerguthaben, anspar_stats = run_ansparphase(depot, )
(alt_depot := Depot()).etfs = [[Anteil(versicherungsguthaben, datetime(ansparphase[1]+1, 1, 1))]]
renten_stats_haupt_var = run_rentenphase(alt_depot, versicherungsguthaben, steuerguthaben)
renten_stats_haupt["etf_var_beitraege"] = renten_stats_haupt_var[f"etf_0_beitraege"]
renten_stats_haupt["etf_var_wachstum"] = renten_stats_haupt_var[f"etf_0_wachstum"]
viz_rentenphase(renten_stats_haupt, f"inkl. Variante Verkauf des Versicherungsdepots zum Renteneintritt", zeige_variante=True)
depot = Depot()
versicherungsguthaben, steuerguthaben, anspar_stats = run_ansparphase(depot, )
(alt_depot := Depot()).etfs = [[Anteil(versicherungsguthaben, datetime(ansparphase[1]+1, 1, 1))]]
renten_stats_alt_var = run_rentenphase(alt_depot, versicherungsguthaben, steuerguthaben, alt_abgeltungssteuer=sim_alt_abgeltungssteuer)
renten_stats_alt["etf_var_beitraege"] = renten_stats_alt_var[f"etf_0_beitraege"]
renten_stats_alt["etf_var_wachstum"] = renten_stats_alt_var[f"etf_0_wachstum"]
viz_rentenphase(renten_stats_alt, f"inkl. Variante Verkauf des Versicherungsdepots zum Renteneintritt - Kapitalertragssteuer {sim_alt_abgeltungssteuer*100:.0f}%", zeige_variante=True)
Für und Wider¶
Qualitativ spricht für eine ETF-basierte Anlage:
- ist flexibler (keine Langzeit-Bindung bei Einzahlung und Flexibilität bei Auszahlung)
- deutlich weniger Kosten und daher höherer Spitzenwert am Ende der Ansparphase. Bei gleicher Entnahme dann längerer Depotbestand.
- Möglichkeit, den Depotbestand weiter zu vererben auch nach Eintritt in die Rente.
Für eine Versicherungsbasierte Anlage spricht:
- ETFs bzw. deren Betreiber (Vanguard/Blackrock) sind wg. intransparenter Geschäfte in die Kritik geraten - bleiben ETFs weiterhin so beliebt?
- Umschichtung in andere Wertpapiere ist bei Versicherungen ohne (weitere) Kosten unkompliziert möglich
- sind resilient ggü. einer verschärften Kapitalertragssteuer-Politik
- Disziplinierungseffekt durch Langzeit-Bindung
- unangreifbar z.B. vom Staat, da zweckgebunden für Altersvorsorge
- bieten durch Umschichtung in konservativere Papiere eine bessere Absicherung im Falle von Kurseinbrüchen bei ETFs im Alter
Den Steuervorteil von versicherungsbasierten Anlagen können diese aber im Vergleich zu den hohen Kosten nicht auspielen, wenn man für ETFs nach der aktuellen Steuergesetzgebung entsprechende Strategien fährt (Teilfreistellung von ETFs, Thessaurierer nutzen, "3x10 Regel").